{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Morse Runner\n",
    "\n",
    "by adqeor @ XJTU EE College\n",
    "\n",
    "rev 0.2\n",
    "\n",
    "按照摩斯码长（难度），从指定的字母集中生成随机训练字符并发音。\n",
    "\n",
    "**用户需要了解的内容使用多行注释**；程序功能注释使用单行注释。\n",
    "\n",
    "## Release History\n",
    "\n",
    "- Prospect\n",
    "    - 使用GitHub保存工程;  \n",
    "    - 引入渐强、渐弱以解决咔哒声;  \n",
    "    - 增强跨平台可移植性;  \n",
    "\n",
    "- rev 0.2, Jan. 6, 2021  \n",
    "    - 将手动指定嘀嗒时序改为从WPM生成时序; \n",
    "    - 修复时间变量名不一致问题;  \n",
    "    - 练习过程可选屏显风格：.-/·—/None;  \n",
    "    - 优化练习过程代码;  \n",
    "    - 优化发行历史格式;  \n",
    "  \n",
    "- rev 0.1, Jan. 5, 2021 [Stored]  \n",
    "    - 实现练习轮数功能;  \n",
    "    - 建立发行历史并开始存档;  \n",
    "  \n",
    "- rev 0.0, Jan. 2, 2021  \n",
    "    - 基于 winsound 实现了基本的发音功能;  \n",
    "    - 单词生成;  \n",
    "\n",
    "## Markdown功能备忘\n",
    "\n",
    "```Javascript\n",
    "let a = 1;\n",
    "const pi = 3.1415;\n",
    "```\n",
    "\n",
    "This is inline equation $j = \\sigma \\cdot T^{4}$.\n",
    "\n",
    "Here goes multiline equation:\n",
    "$$\n",
    "a = b + c\\\\\n",
    "d = e + f\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random, winsound, time, sys\n",
    "\n",
    "randomInitialState = random.getstate() # 储存初始状态，便于后续复现\n",
    "\n",
    "'''\n",
    "alphabet 存储字母的难易等级信息，其包含字母必须为下面 codeMap 的子集\n",
    "使用空格将电码等长的放在一组，方便输入；而后转换为列表方便使用\n",
    "'''\n",
    "alphabet = \"ET IMNA SOR 12345 67890\"\n",
    "# 删除空格并转换为列表\n",
    "alphabet = list(alphabet.replace(' ', ''))\n",
    "\n",
    "'''\n",
    "codeMap 存储字母、符号等到摩斯码的映射关系。数字电码规律性强，后续自动生成\n",
    "'''\n",
    "# 手动输入字母部分\n",
    "# 多行代码可以使用C风格的斜杠；由于此处有{}，也可不加\n",
    "codeMap = {'E': '.', 'T': '-', \\\n",
    "           'I': '..', 'A': '.-', 'N': '-.', 'M': '--', \\\n",
    "           'S': '...', 'O': '---', 'R': '.-.', \\\n",
    "           'D': '-..', 'G': '--.', 'U': '..-', 'W': '.--', \\\n",
    "           'B': '-...', 'C': '-.-.', 'J': '.---'}\n",
    "# 生成数字1-5\n",
    "for i in range(1,6):\n",
    "    numberCode = ['.' for j in range(i)]\n",
    "    numberCode = numberCode + ['-' for j in range(5-i)]\n",
    "    codeMap.update({str(i): ''.join(numberCode)})\n",
    "# 生成数字6-9\n",
    "for i in range(6,10):\n",
    "    numberCode = ['-' for j in range(i-5)]\n",
    "    numberCode = numberCode + ['.' for j in range(10-i)]\n",
    "    codeMap.update({str(i): ''.join(numberCode)})\n",
    "# 生成数字0\n",
    "codeMap.update({'0': '-----'})\n",
    "\n",
    "# frequency in Hz\n",
    "pitch = 580 # Code Tone\n",
    "# Words per Minute\n",
    "wpm = 10\n",
    "# timing in ms or ms/1000, i.e. s\n",
    "t_unit = int(60 * 1000 / (50 * wpm)) # unit time in ms. word 'PARIS' has 50 units\n",
    "t_dit = t_unit # dit duration\n",
    "t_dah = 3 * t_unit # dah duration\n",
    "t_intra_char = t_unit / 1000 # blanking between dits and dahs\n",
    "t_inter_char = 3 * t_unit / 1000 # blanking between chars\n",
    "t_word = 7 * t_unit / 1000 # blanking between words\n",
    "t_hyst = 1200 / 1000 # 按下[Enter]到开始播放的弛豫\n",
    "\n",
    "# 自制enum类型\n",
    "dispStyle_en = 0 # .-.-\n",
    "dispStyle_ch = 1 # ·—·—\n",
    "dispStyle_none = 2 # \n",
    "dispStyle = 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def generateWord(alphaCount, alphaRange):\n",
    "    strList = []\n",
    "    for i in range(alphaCount):\n",
    "        strList.append( alphabet[random.randint(0,alphaRange)] )\n",
    "    return \"\".join(strList) # 'ab'\n",
    "    #return strList            # ['a', 'b']\n",
    "\n",
    "# Note:\n",
    "# winsound 接收 int 型 ms 时间\n",
    "# 而 time.sleep 接收 float 型 s 时间\n",
    "\n",
    "# 句子发音：句子形如'''ab cdd efgh ijk?'''，标点符号分隔句子\n",
    "# 包括句间停顿在内的所有时序都在此函数中实现\n",
    "def play(sentence):\n",
    "    alpha_code = ''\n",
    "#     print('sentence = '+ sentence)\n",
    "    for alpha in sentence:\n",
    "        # 遍历一个句子内的所有字母\n",
    "        alpha_code = codeMap.get(str(alpha.upper()))\n",
    "        # 符号未被码表收录，或遇到空格\n",
    "        if alpha_code == None:\n",
    "            sys.stdout.write(alpha)\n",
    "            time.sleep(t_inter_char)\n",
    "        else:\n",
    "            for element in alpha_code:\n",
    "                # 遍历一个字母的所有嘀嗒\n",
    "                playSingle(element)\n",
    "                # sys.stdout.write('') # 虽然音频的嘀嗒需要时延，但不需要打印出来\n",
    "                # 单个字母，一个嘀嗒完毕\n",
    "        sys.stdout.write(' ')\n",
    "        time.sleep(t_inter_char - t_intra_char)\n",
    "        # 单个字母，所有嘀嗒完毕\n",
    "    sys.stdout.write(' ')\n",
    "    time.sleep(t_word - t_inter_char)\n",
    "    # 一个单词内的所有字母遍历完毕\n",
    "    return\n",
    "\n",
    "def playSingle(element):\n",
    "    if element == '.':\n",
    "        if dispStyle == dispStyle_en:\n",
    "            sys.stdout.write('.')\n",
    "        elif dispStyle == dispStyle_ch:\n",
    "            sys.stdout.write('·')\n",
    "        else:\n",
    "            pass # display nothing by default\n",
    "        winsound.Beep(pitch, t_dit)\n",
    "    else:\n",
    "        if dispStyle == dispStyle_en:\n",
    "            sys.stdout.write('-')\n",
    "        elif dispStyle == dispStyle_ch:\n",
    "            sys.stdout.write('—')\n",
    "        else:\n",
    "            pass # display nothing by default\n",
    "        winsound.Beep(pitch, t_dah)\n",
    "    time.sleep(t_intra_char)\n",
    "    return"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Round1:[Enter]\n",
      "— ··· ——— ———   ·—   · — —— ———   —— ··· · ·    "
     ]
    }
   ],
   "source": [
    "'''\n",
    "上文时序、频率设定在练习小节之重复，便于快速修改\n",
    "默认被注释掉，解除注释以进行更改\n",
    "'''\n",
    "# # frequency in Hz\n",
    "# pitch = 580 # Code Tone\n",
    "# # Words per Minute\n",
    "# wpm = 20\n",
    "# # timing in ms or ms/1000, i.e. s\n",
    "# t_unit = int(60 * 1000 / (50 * wpm)) # unit time in ms. word 'PARIS' has 50 units\n",
    "# t_dit = t_unit # dit duration\n",
    "# t_dah = 3 * t_unit # dah duration\n",
    "# t_intra_char = t_unit / 1000 # blanking between dits and dahs\n",
    "# t_inter_char = 3 * t_unit / 1000 # blanking between chars\n",
    "# t_word = 7 * t_unit / 1000 # blanking between words\n",
    "\n",
    "word_count = 4 # 一次练习的单词数\n",
    "word_len = 3 # 单词平均长度\n",
    "word_len_dev = 2 # 单词长度浮动\n",
    "word = '';\n",
    "\n",
    "testRound = 5 # 练习轮数\n",
    "txDat = []\n",
    "rxDat = []\n",
    "\n",
    "# randomInitialState = random.getstate()\n",
    "random.setstate(randomInitialState) # 测试用，以固定数据集检查程序行为\n",
    "\n",
    "for test_round in range(testRound):\n",
    "    for i in range(word_count):\n",
    "        wordLength = int(random.uniform(word_len - word_len_dev, word_len + word_len_dev))\n",
    "        word = word + generateWord(wordLength, 8)\n",
    "        word = word + ' '\n",
    "    input('\\nRound' + str(test_round + 1) + ':[Enter]')\n",
    "    time.sleep(t_hyst)\n",
    "    play(word)\n",
    "    txDat = txDat + list(word)\n",
    "    rxDat = rxDat + list(input('\\n抄收内容:>'))\n",
    "    print('播放内容:>' + word)\n",
    "    word = '' # 使用完要清零"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
